package edu.calvin.djs32.cs214.interpreters.calculator;

import static org.easymock.EasyMock.expect;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;

import java.util.Map;

import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;

import edu.calvin.cs214.hobbes2008.ApplicationTIR;
import edu.calvin.cs214.hobbes2008.HobbesTIR;
import edu.calvin.cs214.hobbes2008.IdentifierTIR;
import edu.calvin.cs214.hobbes2008.IfTIR;
import edu.calvin.cs214.hobbes2008.IntegerTIR;
import edu.calvin.cs214.hobbes2008.LetTIR;
import edu.calvin.cs214.hobbes2008.StringTIR;
import edu.calvin.cs214.hobbes2008.TupleTIR;
import edu.calvin.cs214.hobbes2008.environments.IEnvironment;
import edu.calvin.djs32.cs214.interpreters.support.EasyMockControl;


public class CalculatorTest {
	private Calculator myCalculator;
	private EasyMockControl myControl;
	private Map<IdentifierTIR,Primitive> myPrimitives;
	private IEnvironment<HobbesTIR> myEnvironment;

	@SuppressWarnings("unchecked")
	@Before
	public void setUp() {
		myControl = new EasyMockControl();
		myEnvironment = myControl.createMock(IEnvironment.class);
		myPrimitives = myControl.createMock(Map.class);
		myCalculator = new Calculator(myPrimitives, myEnvironment);
	}
	
	@Test
	public void ShouldReturnIntegerUntouched(){
		assertEquals(new IntegerTIR(1), myCalculator.visitInteger(new IntegerTIR(1)));
		assertEquals(new IntegerTIR(+2), myCalculator.visitInteger(new IntegerTIR(+2)));
		assertEquals(new IntegerTIR(2), myCalculator.visitInteger(new IntegerTIR(2)));
		assertEquals(new IntegerTIR(0x123f), myCalculator.visitInteger(new IntegerTIR(0x123f)));
	}
	
	@Test
	public void ShouldReturnStringUntoucher(){
		assertEquals(new StringTIR("foo"), myCalculator.visitString(new StringTIR("foo")));
		assertEquals(new StringTIR("bar"), myCalculator.visitString(new StringTIR("bar")));
		assertEquals(new StringTIR("foo bar"), myCalculator.visitString(new StringTIR("foo bar")));
	}
	
	@Test
	public void ShouldReturnTupleUntouched(){
		assertEquals(new TupleTIR(new IntegerTIR(1), new IntegerTIR(2)), myCalculator.visitTuple(new TupleTIR(new IntegerTIR(1), new IntegerTIR(2))));
	}
	
	@Test
	public void shouldCalculateApplication(){
		Primitive primitive = myControl.createMock(Primitive.class);
		HobbesTIR result = myControl.createMock(HobbesTIR.class);
		
		expect( myPrimitives.get(new IdentifierTIR("foo"))
			).andReturn( primitive );
		expect(primitive.apply())
		  .andReturn(result);
		
		myControl.replay();
		
		assertSame(result, myCalculator.visitApplication(new ApplicationTIR(new IdentifierTIR("foo"),
			    new TupleTIR())));
		
		myControl.verify();
	}
	
	@Test
	public void shouldCalculateApplicationWithArguments(){
		Primitive primitive = myControl.createMock(Primitive.class);
		HobbesTIR result = myControl.createMock(HobbesTIR.class);
		HobbesTIR[] arguments = myControl.createMocks(new HobbesTIR[3], HobbesTIR.class);
		IntegerTIR[] evaluatedArguments = new IntegerTIR[]{
				new IntegerTIR(23), new IntegerTIR(42), new IntegerTIR(665)
				};
		
		
		expect( arguments[0].accept(myCalculator) ).andReturn(evaluatedArguments[0]);
		expect( arguments[1].accept(myCalculator) ).andReturn(evaluatedArguments[1]);
		expect( arguments[2].accept(myCalculator) ).andReturn(evaluatedArguments[2]);
		
		expect( myPrimitives.get(new IdentifierTIR("foo") )).andReturn( primitive );
		expect( primitive.apply(evaluatedArguments) ).andReturn(result);

		
		myControl.replay();
		
		assertSame(result, myCalculator.visitApplication(new ApplicationTIR(new IdentifierTIR("foo"),
			    new TupleTIR(arguments))));
		
		myControl.verify();		
	}
	
	@Test
	public void shouldCalculateIfWhenTrue() {
	    HobbesTIR condition = myControl.createMock(HobbesTIR.class);
	    HobbesTIR then = myControl.createMock(HobbesTIR.class);
	    HobbesTIR els = myControl.createMock(HobbesTIR.class);
	    HobbesTIR result = myControl.createMock(HobbesTIR.class);
	    IfTIR ife = new IfTIR(condition, then, els);
	    expect(condition.accept(myCalculator)).andReturn(new IntegerTIR(1));
	    expect(then.accept(myCalculator)).andReturn(result);
	    myControl.replay();
	    assertSame(result, myCalculator.visitIf(ife));
	    myControl.verify();
	}
	
	@Test
	public void shouldCalculateIfWhenFalse() {
	    HobbesTIR condition = myControl.createMock(HobbesTIR.class);
	    HobbesTIR then = myControl.createMock(HobbesTIR.class);
	    HobbesTIR els = myControl.createMock(HobbesTIR.class);
	    HobbesTIR result = myControl.createMock(HobbesTIR.class);
	    IfTIR ife = new IfTIR(condition, then, els);
	    expect(condition.accept(myCalculator)).andReturn(new TupleTIR());
	    expect(els.accept(myCalculator)).andReturn(result);
	    myControl.replay();
	    assertSame(result, myCalculator.visitIf(ife));
	    myControl.verify();
	}
	
	@Test
	public void shouldCalculateIdentifier(){
		HobbesTIR result = myControl.createMock(HobbesTIR.class);
		IdentifierTIR identifier = new IdentifierTIR("+");
		
		expect(myEnvironment.get(EasyMock.same(identifier))).andReturn(result);
		myControl.replay();
		
		assertSame(result, myCalculator.visitIdentifier(identifier));
		
		myControl.verify();
	}
	
	@Test
	public void shouldCalculateLet(){
		HobbesTIR result = myControl.createMock(HobbesTIR.class);
		IdentifierTIR identifier = new IdentifierTIR("+");
		HobbesTIR initialization = myControl.createMock(HobbesTIR.class);
		HobbesTIR body = myControl.createMock(HobbesTIR.class);
		HobbesTIR value = myControl.createMock(HobbesTIR.class);
		IEnvironment<HobbesTIR> nestedEnvironment = myControl.createMock(IEnvironment.class);
		
		expect(initialization.accept(myCalculator)).andReturn(value);
		expect(myEnvironment.create()).andReturn(nestedEnvironment);
		nestedEnvironment.add(EasyMock.same(identifier), EasyMock.same(value));
		Calculator nestedCalculator = new Calculator(myCalculator.getPrimitives(), nestedEnvironment);
		expect(body.accept(nestedCalculator)).andReturn(result);


		myControl.replay();
		assertSame(result, myCalculator.visitLet(new LetTIR(identifier, initialization, body)));
		myControl.verify();
		
	}

}
